// map.cpp
//

#include "map.hpp"

#include <random>



// ZMap::ZMap
//
ZMap::ZMap ( int width,
             int height )
  : width(width),
    height(height)
{
  cells.resize(height);
  cells.shrink_to_fit();
  for (int y = 0; y < height; y++)
    {
      cells[y].resize(width, 0.f);
      cells[y].shrink_to_fit();
    }
}



// ZMap::swap
//
void ZMap::swap ( ZMap &other )
{
  GLT_ASSERT(width == other.width);
  GLT_ASSERT(height == other.height);

  cells.swap(other.cells);
}



// ZMap::randomize
//
void ZMap::randomize ( float max_height )
{
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_real_distribution<> dis{0.f, max_height};
  
  
  for (int y = 0; y < height; ++y)
    {
      for (int x = 0; x < width; ++x)
        {
          cells[y][x] = dis(gen);
        }
    }
}



// ZMap::smooth
//
void ZMap::smooth ( int passes )
{
  ZMap zmap2{width, height};

  for (int p = 0; p < passes; ++p)
    {
      for (int y = 0; y < height; ++y)
        {
          for (int x = 0; x < width; ++x)
            {
              float zsum = 0.f;
              int nz = 0;
              for (int j = -1; j <= 1; ++j)
                {
                  for (int i = - 1; i < 1; i++)
                    {
                      int xi = x + i;
                      int yj = y + j;
                      if (xi >= 0 && xi < width && yj >= 0 && yj < height)
                        {
                          zsum += cells[yj][xi];
                          nz++;
                        }
                    }
                }
              zmap2.cells[y][x] = zsum / float(nz);
            }
        }
      swap(zmap2);
    }
}



// ZMap::level
//
void ZMap::level ( float lvl )
{
  float zmin = 1000000.f;
  for (int y = 0; y < height; ++y)
    for (int x = 0; x < width; ++x)
      zmin = std::min(zmin, cells[y][x]);
  zmin -= lvl;
  for (int y = 0; y < height; ++y)
    for (int x = 0; x < width; ++x)
      cells[y][x] -= zmin;
}



// Map::Map
//
Map::Map ()
  : width(128),
    height(128)
{
  int width_ext = width + 2;
  int height_ext = height + 2;
  int size_ext = width_ext * height_ext;

  stride = width_ext;
  
  raw_cells.resize(size_ext);
  raw_cells.shrink_to_fit();
  cells.resize(height_ext);
  cells.shrink_to_fit();
  MapCell *line = raw_cells.data() + stride + 1;
  for (int y = 0; y < height_ext; y++)
    {
      cells[y] = line;
      line += stride;
    }
}



// Map::apply_zmap
//
void Map::apply_zmap ( ZMap &zmap )
{
  GLT_ASSERT(zmap.get_width() == (width + 1));
  GLT_ASSERT(zmap.get_height() == (height + 1));

  for (int y = 0; y <= height; y++)
    for (int x = 0; x <= width; x++)
      cells[y][x].altitude = zmap.get_z(x, y);
}



// Map::update_normals
//
void Map::update_normals ()
{
  for (int y = 0; y <= height; y++)
    for (int x = 0; x <= width; x++)
      update_normal_flat(x, y);
}



// Map::update_normal_flat
//
void Map::update_normal_flat ( int x,
                               int y )
{
  MapCell *a = &cells[y][x];
  MapCell *b = a + 1;
  MapCell *c = a + stride + 1;
  MapCell *d = a + stride;

  glm::vec3 va{0.f, a->altitude, 0.f};
  glm::vec3 vb{1.f, b->altitude, 0.f};
  glm::vec3 vc{1.f, c->altitude, 1.f};
  glm::vec3 vd{0.f, d->altitude, 1.f};
  
  glm::vec3 na = glm::cross(vd-va, vb-va);
  glm::vec3 nb = glm::cross(va-vb, vc-vb);
  glm::vec3 nc = glm::cross(vb-va, vd-va);
  glm::vec3 nd = glm::cross(vc-vd, va-vd);

  a->normal = glm::normalize(na + nb + nc + nd);
}



// Map::smooth_normals
//
void Map::smooth_normals ()
{
  for (int y = 0; y < height; ++y)
    {
      for (int x = 0; x < width; ++x)
        {
          MapCell *c0 = &cells[y][x];
          MapCell *c1 = c0 - 1;
          MapCell *c2 = c1 - stride;
          MapCell *c3 = c2 + 1;
          c0->smooth_normal = glm::normalize(c0->normal +
                                             c1->normal +
                                             c2->normal +
                                             c3->normal);
        }
    }
}



// Map::get_cell_normals
//
void Map::get_cell_normals ( int x,
                             int y,
                             glm::vec3 *normals )
{
  MapCell *cell = &cells[y][x];

  // // flat
  // normals[0] = cell->normal;
  // normals[1] = cell->normal;
  // normals[2] = cell->normal;
  // normals[3] = cell->normal;

  // smooth
  normals[0] = cell->smooth_normal;
  normals[1] = (cell + 1)->smooth_normal;
  normals[2] = (cell + stride + 1)->smooth_normal;
  normals[3] = (cell + stride)->smooth_normal;
}



// Map::create_mesh
//
void Map::create_mesh ( wgl::Mesh &mesh )
{
  std::vector<wgl::Vertex> vertices;
  std::vector<GLuint> indices;
  // int idx_stride = width + 1;
  GLuint index = 0;
  glm::vec3 color{1.f, 1.f, 1.f};
  for (int y = 0; y < height; ++y)
    {
      for (int x = 0; x < width; ++x)
        {
          int x1 = x+1;
          int y1 = y+1;

          glm::vec3 normals[4];
          get_cell_normals(x, y, normals);
          
          vertices.emplace_back(glm::vec3{float(x), cells[y][x].altitude, float(y)},
                                color,
                                normals[0],
                                glm::vec2{0.f, 0.f});
          
          vertices.emplace_back(glm::vec3{float(x1), cells[y][x1].altitude, float(y)},
                                color,
                                normals[1],
                                glm::vec2{1.f, 0.f});

          vertices.emplace_back(glm::vec3{float(x1), cells[y1][x1].altitude, float(y1)},
                                color,
                                normals[2],
                                glm::vec2{1.f, 1.f});
                                
          vertices.emplace_back(glm::vec3{float(x), cells[y1][x].altitude, float(y1)},
                                color,
                                normals[3],
                                glm::vec2{0.f, 1.f});

          // if (x != width && y != height)
          //   {
              indices.push_back(index);
              indices.push_back(index+2);
              indices.push_back(index+1);

              indices.push_back(index);
              indices.push_back(index+3);
              indices.push_back(index+2);
            // }
          
          index += 4;
        }
    }

  mesh.swap(GL_TRIANGLES, vertices, indices);
}



// Map::create_grid_mesh
//
void Map::create_grid_mesh ( wgl::Mesh &mesh,
                             glm::vec3 color )
{
  std::vector<wgl::Vertex> vertices;
  std::vector<GLuint> indices;
  int idx_stride = width + 1;
  GLuint index = 0;
  for (int y = 0; y <= height; ++y)
    {
      for (int x = 0; x <= width; ++x)
        {
          vertices.emplace_back(glm::vec3{float(x), cells[y][x].altitude + .05f, float(y)},
                                color,
                                cells[y][x].normal,
                                glm::vec2{float(x), float(y)});

          if (x != width && y != height)
            {
              indices.push_back(index);
              indices.push_back(index+1);
              indices.push_back(index);
              indices.push_back(index+idx_stride);
            }
          
          ++index;
        }
    }

  mesh.swap(GL_LINES, vertices, indices);
}
